{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0f340049",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "c:\\ProgramData\\miniconda3\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n",
      "`torch_dtype` is deprecated! Use `dtype` instead!\n",
      "`torch_dtype` is deprecated! Use `dtype` instead!\n",
      "Loading checkpoint shards: 100%|██████████| 2/2 [00:03<00:00,  1.68s/it]\n",
      "Device set to use cuda:0\n",
      "Loading checkpoint shards: 100%|██████████| 2/2 [00:03<00:00,  1.68s/it]\n",
      "Device set to use cuda:0\n"
     ]
    },
    {
     "ename": "TypeError",
     "evalue": "'ChatState' object is not subscriptable",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mTypeError\u001b[39m                                 Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 95\u001b[39m\n\u001b[32m     93\u001b[39m \u001b[38;5;66;03m# 5. 编译并单次调用示例\u001b[39;00m\n\u001b[32m     94\u001b[39m app = workflow.compile()\n\u001b[32m---> \u001b[39m\u001b[32m95\u001b[39m result = \u001b[43mapp\u001b[49m\u001b[43m.\u001b[49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\u001b[43m{\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mmessages\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43m[\u001b[49m\u001b[43mHumanMessage\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcontent\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m你好，介绍一下你自己？\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m]\u001b[49m\u001b[43m}\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m     96\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33mSingle invoke result:\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m     97\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m m \u001b[38;5;129;01min\u001b[39;00m result[\u001b[33m\"\u001b[39m\u001b[33mmessages\u001b[39m\u001b[33m\"\u001b[39m]:\n",
      "\u001b[36mFile \u001b[39m\u001b[32m~\\AppData\\Roaming\\Python\\Python313\\site-packages\\langgraph\\pregel\\main.py:3094\u001b[39m, in \u001b[36mPregel.invoke\u001b[39m\u001b[34m(self, input, config, context, stream_mode, print_mode, output_keys, interrupt_before, interrupt_after, durability, **kwargs)\u001b[39m\n\u001b[32m   3091\u001b[39m chunks: \u001b[38;5;28mlist\u001b[39m[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any] | Any] = []\n\u001b[32m   3092\u001b[39m interrupts: \u001b[38;5;28mlist\u001b[39m[Interrupt] = []\n\u001b[32m-> \u001b[39m\u001b[32m3094\u001b[39m \u001b[43m\u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mstream\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m   3095\u001b[39m \u001b[43m    \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m   3096\u001b[39m \u001b[43m    \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   3097\u001b[39m \u001b[43m    \u001b[49m\u001b[43mcontext\u001b[49m\u001b[43m=\u001b[49m\u001b[43mcontext\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   3098\u001b[39m \u001b[43m    \u001b[49m\u001b[43mstream_mode\u001b[49m\u001b[43m=\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mupdates\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mvalues\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\n\u001b[32m   3099\u001b[39m \u001b[43m    \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mstream_mode\u001b[49m\u001b[43m \u001b[49m\u001b[43m==\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mvalues\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\n\u001b[32m   3100\u001b[39m \u001b[43m    \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mstream_mode\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   3101\u001b[39m \u001b[43m    \u001b[49m\u001b[43mprint_mode\u001b[49m\u001b[43m=\u001b[49m\u001b[43mprint_mode\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   3102\u001b[39m \u001b[43m    \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[43m=\u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   3103\u001b[39m \u001b[43m    \u001b[49m\u001b[43minterrupt_before\u001b[49m\u001b[43m=\u001b[49m\u001b[43minterrupt_before\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   3104\u001b[39m \u001b[43m    \u001b[49m\u001b[43minterrupt_after\u001b[49m\u001b[43m=\u001b[49m\u001b[43minterrupt_after\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   3105\u001b[39m \u001b[43m    \u001b[49m\u001b[43mdurability\u001b[49m\u001b[43m=\u001b[49m\u001b[43mdurability\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   3106\u001b[39m \u001b[43m    \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   3107\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[32m   3108\u001b[39m \u001b[43m    \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mstream_mode\u001b[49m\u001b[43m \u001b[49m\u001b[43m==\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mvalues\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\n\u001b[32m   3109\u001b[39m \u001b[43m        \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mlen\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[43m==\u001b[49m\u001b[43m \u001b[49m\u001b[32;43m2\u001b[39;49m\u001b[43m:\u001b[49m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m~\\AppData\\Roaming\\Python\\Python313\\site-packages\\langgraph\\pregel\\main.py:2679\u001b[39m, in \u001b[36mPregel.stream\u001b[39m\u001b[34m(self, input, config, context, stream_mode, print_mode, output_keys, interrupt_before, interrupt_after, durability, subgraphs, debug, **kwargs)\u001b[39m\n\u001b[32m   2677\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m task \u001b[38;5;129;01min\u001b[39;00m loop.match_cached_writes():\n\u001b[32m   2678\u001b[39m     loop.output_writes(task.id, task.writes, cached=\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[32m-> \u001b[39m\u001b[32m2679\u001b[39m \u001b[43m\u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43m_\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mrunner\u001b[49m\u001b[43m.\u001b[49m\u001b[43mtick\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m   2680\u001b[39m \u001b[43m    \u001b[49m\u001b[43m[\u001b[49m\u001b[43mt\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mt\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mloop\u001b[49m\u001b[43m.\u001b[49m\u001b[43mtasks\u001b[49m\u001b[43m.\u001b[49m\u001b[43mvalues\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mnot\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mt\u001b[49m\u001b[43m.\u001b[49m\u001b[43mwrites\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   2681\u001b[39m \u001b[43m    \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mstep_timeout\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   2682\u001b[39m \u001b[43m    \u001b[49m\u001b[43mget_waiter\u001b[49m\u001b[43m=\u001b[49m\u001b[43mget_waiter\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   2683\u001b[39m \u001b[43m    \u001b[49m\u001b[43mschedule_task\u001b[49m\u001b[43m=\u001b[49m\u001b[43mloop\u001b[49m\u001b[43m.\u001b[49m\u001b[43maccept_push\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   2684\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[32m   2685\u001b[39m \u001b[43m    \u001b[49m\u001b[38;5;66;43;03m# emit output\u001b[39;49;00m\n\u001b[32m   2686\u001b[39m \u001b[43m    \u001b[49m\u001b[38;5;28;43;01myield from\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43m_output\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m   2687\u001b[39m \u001b[43m        \u001b[49m\u001b[43mstream_mode\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mprint_mode\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msubgraphs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mqueue\u001b[49m\u001b[43m.\u001b[49m\u001b[43mEmpty\u001b[49m\n\u001b[32m   2688\u001b[39m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m   2689\u001b[39m loop.after_tick()\n",
      "\u001b[36mFile \u001b[39m\u001b[32m~\\AppData\\Roaming\\Python\\Python313\\site-packages\\langgraph\\pregel\\_runner.py:167\u001b[39m, in \u001b[36mPregelRunner.tick\u001b[39m\u001b[34m(self, tasks, reraise, timeout, retry_policy, get_waiter, schedule_task)\u001b[39m\n\u001b[32m    165\u001b[39m t = tasks[\u001b[32m0\u001b[39m]\n\u001b[32m    166\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m167\u001b[39m     \u001b[43mrun_with_retry\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m    168\u001b[39m \u001b[43m        \u001b[49m\u001b[43mt\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    169\u001b[39m \u001b[43m        \u001b[49m\u001b[43mretry_policy\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    170\u001b[39m \u001b[43m        \u001b[49m\u001b[43mconfigurable\u001b[49m\u001b[43m=\u001b[49m\u001b[43m{\u001b[49m\n\u001b[32m    171\u001b[39m \u001b[43m            \u001b[49m\u001b[43mCONFIG_KEY_CALL\u001b[49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mpartial\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m    172\u001b[39m \u001b[43m                \u001b[49m\u001b[43m_call\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    173\u001b[39m \u001b[43m                \u001b[49m\u001b[43mweakref\u001b[49m\u001b[43m.\u001b[49m\u001b[43mref\u001b[49m\u001b[43m(\u001b[49m\u001b[43mt\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    174\u001b[39m \u001b[43m                \u001b[49m\u001b[43mretry_policy\u001b[49m\u001b[43m=\u001b[49m\u001b[43mretry_policy\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    175\u001b[39m \u001b[43m                \u001b[49m\u001b[43mfutures\u001b[49m\u001b[43m=\u001b[49m\u001b[43mweakref\u001b[49m\u001b[43m.\u001b[49m\u001b[43mref\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfutures\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    176\u001b[39m \u001b[43m                \u001b[49m\u001b[43mschedule_task\u001b[49m\u001b[43m=\u001b[49m\u001b[43mschedule_task\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    177\u001b[39m \u001b[43m                \u001b[49m\u001b[43msubmit\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43msubmit\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    178\u001b[39m \u001b[43m            \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    179\u001b[39m \u001b[43m        \u001b[49m\u001b[43m}\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    180\u001b[39m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    181\u001b[39m     \u001b[38;5;28mself\u001b[39m.commit(t, \u001b[38;5;28;01mNone\u001b[39;00m)\n\u001b[32m    182\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m exc:\n",
      "\u001b[36mFile \u001b[39m\u001b[32m~\\AppData\\Roaming\\Python\\Python313\\site-packages\\langgraph\\pregel\\_retry.py:42\u001b[39m, in \u001b[36mrun_with_retry\u001b[39m\u001b[34m(task, retry_policy, configurable)\u001b[39m\n\u001b[32m     40\u001b[39m     task.writes.clear()\n\u001b[32m     41\u001b[39m     \u001b[38;5;66;03m# run the task\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m42\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mtask\u001b[49m\u001b[43m.\u001b[49m\u001b[43mproc\u001b[49m\u001b[43m.\u001b[49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtask\u001b[49m\u001b[43m.\u001b[49m\u001b[43minput\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m     43\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m ParentCommand \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[32m     44\u001b[39m     ns: \u001b[38;5;28mstr\u001b[39m = config[CONF][CONFIG_KEY_CHECKPOINT_NS]\n",
      "\u001b[36mFile \u001b[39m\u001b[32m~\\AppData\\Roaming\\Python\\Python313\\site-packages\\langgraph\\_internal\\_runnable.py:656\u001b[39m, in \u001b[36mRunnableSeq.invoke\u001b[39m\u001b[34m(self, input, config, **kwargs)\u001b[39m\n\u001b[32m    654\u001b[39m     \u001b[38;5;66;03m# run in context\u001b[39;00m\n\u001b[32m    655\u001b[39m     \u001b[38;5;28;01mwith\u001b[39;00m set_config_context(config, run) \u001b[38;5;28;01mas\u001b[39;00m context:\n\u001b[32m--> \u001b[39m\u001b[32m656\u001b[39m         \u001b[38;5;28minput\u001b[39m = \u001b[43mcontext\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[43mstep\u001b[49m\u001b[43m.\u001b[49m\u001b[43minvoke\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    657\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m    658\u001b[39m     \u001b[38;5;28minput\u001b[39m = step.invoke(\u001b[38;5;28minput\u001b[39m, config)\n",
      "\u001b[36mFile \u001b[39m\u001b[32m~\\AppData\\Roaming\\Python\\Python313\\site-packages\\langgraph\\_internal\\_runnable.py:400\u001b[39m, in \u001b[36mRunnableCallable.invoke\u001b[39m\u001b[34m(self, input, config, **kwargs)\u001b[39m\n\u001b[32m    398\u001b[39m         run_manager.on_chain_end(ret)\n\u001b[32m    399\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m400\u001b[39m     ret = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    401\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.recurse \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(ret, Runnable):\n\u001b[32m    402\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m ret.invoke(\u001b[38;5;28minput\u001b[39m, config)\n",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 83\u001b[39m, in \u001b[36mchatbotByHF\u001b[39m\u001b[34m(state)\u001b[39m\n\u001b[32m     74\u001b[39m pipe = pipeline(\n\u001b[32m     75\u001b[39m     \u001b[33m\"\u001b[39m\u001b[33mtext-generation\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m     76\u001b[39m     model=model,\n\u001b[32m   (...)\u001b[39m\u001b[32m     80\u001b[39m     do_sample=\u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[32m     81\u001b[39m )\n\u001b[32m     82\u001b[39m llm = HuggingFacePipeline(pipeline=pipe)\n\u001b[32m---> \u001b[39m\u001b[32m83\u001b[39m response = llm.invoke(\u001b[43mstate\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mmessages\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m)\n\u001b[32m     84\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m {\u001b[33m\"\u001b[39m\u001b[33mmessages\u001b[39m\u001b[33m\"\u001b[39m: [response]}\n",
      "\u001b[31mTypeError\u001b[39m: 'ChatState' object is not subscriptable",
      "During task with name 'chatbot' and id 'c2ad0189-93e8-dd8c-e5bd-73e8d68a8748'"
     ]
    }
   ],
   "source": [
    "# 状态流（state_flow）改进：使用 Pydantic + 动态 LLM 回退\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from langgraph.graph.message import add_messages\n",
    "from typing import Annotated, List, Dict, Any\n",
    "from pydantic import BaseModel, ValidationError\n",
    "from langchain_core.messages import HumanMessage, AIMessage, BaseMessage\n",
    "import requests, json, time\n",
    "\n",
    "# 1. 定义 Pydantic 状态模型（messages 使用 reducer 追加）\n",
    "class ChatState(BaseModel):\n",
    "    messages: Annotated[List[BaseMessage], add_messages] = []\n",
    "\n",
    "def _coerce_state(state: Dict[str, Any] | ChatState) -> ChatState:\n",
    "    if isinstance(state, ChatState):\n",
    "        return state\n",
    "    try:\n",
    "        return ChatState(**state)\n",
    "    except ValidationError as e:\n",
    "        raise ValueError(f\"State validation error: {e}\")\n",
    "\n",
    "# 2. 初始化 LLM：优先 Ollama，本地不可用时回退到简单模板\n",
    "def _ollama_available(model: str = \"qwen3:8b\") -> bool:\n",
    "    try:\n",
    "        r = requests.get(\"http://localhost:11434/api/tags\", timeout=2)\n",
    "        if r.status_code == 200:\n",
    "            data = r.json()\n",
    "            return any(tag.get('name') == model for tag in data.get('models', []) or data.get('tags', []))\n",
    "        return False\n",
    "    except Exception:\n",
    "        return False\n",
    "\n",
    "def generate_llm(messages: List[BaseMessage]) -> AIMessage:\n",
    "    # 将最近一条用户消息作为 prompt\n",
    "    last_user = \"\"\n",
    "    for m in reversed(messages):\n",
    "        if isinstance(m, HumanMessage):\n",
    "            last_user = m.content\n",
    "            break\n",
    "    if _ollama_available():\n",
    "        try:\n",
    "            payload = {\"model\": \"qwen3:8b\", \"prompt\": last_user, \"stream\": False}\n",
    "            resp = requests.post(\"http://localhost:11434/api/generate\", json=payload, timeout=30)\n",
    "            if resp.status_code == 200:\n",
    "                text = resp.json().get(\"response\", \"\")\n",
    "                return AIMessage(content=text)\n",
    "            else:\n",
    "                return AIMessage(content=f\"[ollama error {resp.status_code}] echo: {last_user}\")\n",
    "        except Exception as e:\n",
    "            return AIMessage(content=f\"[ollama exception] {e} :: {last_user}\")\n",
    "    # 回退：简单规则生成\n",
    "    return AIMessage(content=f\"(fallback) 收到: {last_user}\")\n",
    "\n",
    "# 3. 聊天节点\n",
    "def chatbot(state: Dict[str, Any] | ChatState):\n",
    "    st = _coerce_state(state)\n",
    "    ai = generate_llm(st.messages)\n",
    "    # 返回增量（仅新消息）\n",
    "    return {\"messages\": [ai]}\n",
    "\n",
    "from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline\n",
    "from langchain_huggingface import HuggingFacePipeline\n",
    "\n",
    "def chatbotByHF(state: Dict[str, Any] | ChatState):\n",
    "    st = _coerce_state(state)\n",
    "    model_id = \"E:/huggingface_models/qwen/Qwen3-1.7B\"  # 或本地路径：\"/path/to/your/model\"\n",
    "\n",
    "    tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)\n",
    "    model = AutoModelForCausalLM.from_pretrained(\n",
    "        model_id,\n",
    "        device_map=\"auto\",      # 自动使用 GPU（如果有）\n",
    "        torch_dtype=\"auto\",     # 自动选择 float16 / bfloat16\n",
    "        trust_remote_code=True\n",
    "    )\n",
    "\n",
    "    pipe = pipeline(\n",
    "        \"text-generation\",\n",
    "        model=model,\n",
    "        tokenizer=tokenizer,\n",
    "        max_new_tokens=256,\n",
    "        temperature=0.7,\n",
    "        do_sample=True\n",
    "    )\n",
    "    llm = HuggingFacePipeline(pipeline=pipe)\n",
    "    response = llm.invoke(st.messages)\n",
    "    return {\"messages\": [response]}\n",
    "\n",
    "\n",
    "# 4. 构建图\n",
    "workflow = StateGraph(ChatState)\n",
    "workflow.add_node(\"chatbot\", chatbotByHF)\n",
    "workflow.add_edge(START, \"chatbot\")\n",
    "workflow.add_edge(\"chatbot\", END)\n",
    "\n",
    "# 5. 编译并单次调用示例\n",
    "app = workflow.compile()\n",
    "result = app.invoke({\"messages\": [HumanMessage(content=\"你好，介绍一下你自己？\")]})\n",
    "print(\"Single invoke result:\")\n",
    "for m in result[\"messages\"]:\n",
    "    print(m.type, \"=>\", m.content)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
